package com.hzncc.flowable_diboot.service.bpm.impl;

import cn.hutool.core.util.StrUtil;
import com.diboot.core.vo.Pagination;
import com.diboot.iam.entity.BaseLoginUser;
import com.diboot.iam.util.IamSecurityUtils;
import com.hzncc.flowable_diboot.convert.bpm.BpmModelConvert;
import com.hzncc.flowable_diboot.dto.bpm.BpmModelMetaInfoRespDTO;
import com.hzncc.flowable_diboot.entity.FormDesign;
import com.hzncc.flowable_diboot.entity.bpm.BpmCategory;
import com.hzncc.flowable_diboot.entity.bpm.BpmForm;
import com.hzncc.flowable_diboot.enums.FormTypeEnum;
import com.hzncc.flowable_diboot.exception.BusinessException;
import com.hzncc.flowable_diboot.flowable.candidate.BpmTaskCandidateInvoker;
import com.hzncc.flowable_diboot.service.FormDesignService;
import com.hzncc.flowable_diboot.service.bpm.BpmCategoryService;
import com.hzncc.flowable_diboot.service.bpm.BpmFormService;
import com.hzncc.flowable_diboot.service.bpm.BpmModelService;
import com.hzncc.flowable_diboot.service.bpm.BpmProcessDefinitionService;
import com.hzncc.flowable_diboot.util.BpmnModelUtils;
import com.hzncc.flowable_diboot.util.JsonUtils;
import com.hzncc.flowable_diboot.vo.bpm.BpmModelCreateReqVO;
import com.hzncc.flowable_diboot.vo.bpm.BpmModelReqVO;
import com.hzncc.flowable_diboot.vo.bpm.BpmModelRespVO;
import com.hzncc.flowable_diboot.vo.bpm.BpmModelUpdateReqVO;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.bpmn.model.StartEvent;
import org.flowable.bpmn.model.UserTask;
import org.flowable.common.engine.impl.db.SuspensionState;
import org.flowable.engine.RepositoryService;
import org.flowable.engine.repository.*;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import jakarta.annotation.Resource;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @Description : 流程模型业务实现
 * @Date : 2024/6/20
 * @Author : ChenJin
 * @Version : v1.0.0
 **/
@Service
public class BpmModelServiceImpl implements BpmModelService {

    @Resource
    private RepositoryService repositoryService;

    @Resource
    private BpmProcessDefinitionService bpmProcessDefinitionService;

    @Resource
    private FormDesignService formDesignService;
    
    @Resource
    private BpmCategoryService bpmCategoryService;

    @Resource
    private BpmTaskCandidateInvoker taskCandidateInvoker;

    @Override
    public List<BpmModelRespVO> getBpmModelPage(BpmModelReqVO reqVO, Pagination pagination) {
        BaseLoginUser currentUser = IamSecurityUtils.getCurrentUser();
        //根据条件查询流程模型
        ModelQuery modelQuery = repositoryService.createModelQuery();
        modelQuery.modelTenantId(currentUser.getTenantId());
        if (StrUtil.isNotBlank(reqVO.getKey())) {
            modelQuery.modelKey(reqVO.getKey());
        }
        if (StrUtil.isNotBlank(reqVO.getName())) {
            modelQuery.modelNameLike("%" + reqVO.getName() + "%"); // 模糊匹配
        }
        if (StrUtil.isNotBlank(reqVO.getCategory())) {
            modelQuery.modelCategory(reqVO.getCategory());
        }
        long count = modelQuery.count();
        pagination.setTotalCount(count);
        if (count == 0) {
            return new ArrayList<>();
        }
        List<Model> models = modelQuery.orderByCreateTime().desc().listPage(pagination.getPageIndex()-1, pagination.getPageSize());
        if (CollectionUtils.isEmpty(models)){
            return new ArrayList<>();
        }
        //查询关联表单
        List<BpmModelMetaInfoRespDTO> metaInfoList = models.stream().map(BpmModelConvert.INSTANCE::buildMetaInfo).collect(Collectors.toList());
        List<FormDesign> formList = formDesignService.getEntityListByIds(Arrays.asList(metaInfoList.stream().map(BpmModelMetaInfoRespDTO::getFormId).distinct().toArray()));
        Map<String, FormDesign> idToFormMap = formList.stream().collect(Collectors.toMap(FormDesign::getId, form -> form));
        //查询流程所属分类
        List<BpmCategory> categoryList = bpmCategoryService.getEntityListByIds(models.stream().map(Model::getCategory).distinct().collect(Collectors.toList()));
        Map<String, BpmCategory> idToCategoryMap = categoryList.stream().collect(Collectors.toMap(BpmCategory::getId, bpmCategory -> bpmCategory));
        //查询流程部署（最新部署）
        List<Deployment> deploymentList = new ArrayList<>();
        List<ProcessDefinition> processDefinitionList = new ArrayList<>();
        models.forEach(model -> {
            if (StringUtils.isNotEmpty(model.getDeploymentId())){
                Deployment deployment = repositoryService.createDeploymentQuery().deploymentId(model.getDeploymentId()).singleResult();
                deploymentList.add(deployment);
                ProcessDefinition processDefinitionByDeploymentId = repositoryService.createProcessDefinitionQuery().deploymentId(model.getDeploymentId()).singleResult();
                processDefinitionList.add(processDefinitionByDeploymentId);
            }
        });
        Map<String, Deployment> idToDeploymentMap = deploymentList.stream().collect(Collectors.toMap(Deployment::getId, Deployment->Deployment));
        Map<String, ProcessDefinition> idToProcessDefinitionMap = processDefinitionList.stream().collect(Collectors.toMap(ProcessDefinition::getDeploymentId,processDefinition-> processDefinition));
        return BpmModelConvert.INSTANCE.buildModelList(models,idToFormMap,idToCategoryMap,idToDeploymentMap,idToProcessDefinitionMap);
    }

    @Override
    public BpmModelRespVO getBpmModelInfo(String id) {
        Model model = repositoryService.getModel(id);
        byte[] modelEditorSource = repositoryService.getModelEditorSource(id);
        BpmModelRespVO bpmModelRespVO = BpmModelConvert.INSTANCE.buildModel(model, modelEditorSource);
        if (StringUtils.isNotEmpty(model.getDeploymentId())){
            bpmModelRespVO.setStatus("1");
        }
        return bpmModelRespVO;
    }

    @Override
    @Transactional
    public String createModel(BpmModelCreateReqVO modelVO, String bpmnXml) {
        BaseLoginUser currentUser = IamSecurityUtils.getCurrentUser();
        //检验流程标识已存在
        Model modelByKey = this.getModelByKey(modelVO.getKey());
        if(ObjectUtils.isNotEmpty(modelByKey)){
            throw new BusinessException("流程标识已存在");
        }
        //创建流程定义
        Model model = repositoryService.newModel();
        BpmModelConvert.INSTANCE.buildModelFromCreateReq(model,modelVO);
        model.setTenantId(currentUser.getTenantId());
        repositoryService.saveModel(model);
        saveModelBpmnXml(model,bpmnXml);
        return model.getId();
    }

    @Override
    @Transactional
    public String updateModel(BpmModelUpdateReqVO modelVO, String bpmnXml) {
        //校验流程模型是否存在
        Model model = getModelById(modelVO.getId());
        if(ObjectUtils.isEmpty(model)){
            throw new BusinessException("流程模型不存在！");
        }
        //更新流程模型
        BpmModelConvert.INSTANCE.buildModelFromUpdateReq(model,modelVO);
        repositoryService.saveModel(model);
        saveModelBpmnXml(model,modelVO.getBpmnXml());
        return model.getId();
    }

    @Override
    @Transactional
    public void deleteModel(String modelId) {
        //校验流程模型是否存在
        Model model = getModelById(modelId);
        if(ObjectUtils.isEmpty(model)){
            throw new BusinessException("流程模型不存在！");
        }
        //修改流程定义状态,将关联的流程定义挂起
        if (StrUtil.isNotEmpty(model.getDeploymentId())) {
            ProcessDefinition oldDefinition = repositoryService.createProcessDefinitionQuery().deploymentId(model.getDeploymentId()).singleResult();
            if (ObjectUtils.isNotEmpty(oldDefinition)){
                bpmProcessDefinitionService.updateProcessDefinitionState(oldDefinition.getId(),SuspensionState.SUSPENDED.getStateCode());
            }
        }
        //删除流程定义
        repositoryService.deleteModel(modelId);
    }

    @Override
    @Transactional
    public String deployModel(String modelId){
        //判断流程模型是否存在
        Model model = getModelById(modelId);
        if(ObjectUtils.isEmpty(model)){
            throw new BusinessException("流程模型不存在！");
        }
        //校验流程模型是否有流程图
        byte[] bpmnXmlByte = getModelBpmnXML(modelId);
        validateBpmnXml(bpmnXmlByte);
        //校验流程是否有流程表单
        BpmModelMetaInfoRespDTO metaInfo = JsonUtils.parseObject(model.getMetaInfo(), BpmModelMetaInfoRespDTO.class);
        FormDesign formDesign = validateFormConfig(metaInfo);
        //校验任务分配规则是否配置
        taskCandidateInvoker.validateBpmnConfig(bpmnXmlByte);
        //已部署的流程挂起
        if (StrUtil.isNotEmpty(model.getDeploymentId())) {
            ProcessDefinition oldDefinition = repositoryService.createProcessDefinitionQuery().deploymentId(model.getDeploymentId()).singleResult();
            if (ObjectUtils.isNotEmpty(oldDefinition)){
                bpmProcessDefinitionService.updateProcessDefinitionState(oldDefinition.getId(),SuspensionState.SUSPENDED.getStateCode());
            }
        }
        //创建流程定义并部署
        String definitionId = bpmProcessDefinitionService.createProcessDefinition(model,metaInfo,bpmnXmlByte,formDesign);
        //更新model绑定的deploymentId
        ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery().processDefinitionId(definitionId).singleResult();
        model.setDeploymentId(processDefinition.getDeploymentId());
        repositoryService.saveModel(model);
        return definitionId;
    }

    @Override
    @Transactional
    public void updateModelState(String id, Integer state) {
        //校验流程模型存在
        Model model = repositoryService.getModel(id);
        if (model == null) {
            throw new BusinessException("流程模型不存在");
        }
        //校验流程定义存在
        ProcessDefinition definition = repositoryService.createProcessDefinitionQuery().deploymentId(model.getDeploymentId()).singleResult();
        if (definition == null) {
            throw new BusinessException("流程定义不存在");
        }
        //更新状态
        bpmProcessDefinitionService.updateProcessDefinitionState(definition.getId(), state);
    }

    @Override
    public Model getModelById(String modelId) {
        return repositoryService.getModel(modelId);
    }

    @Override
    public Model getModelByKey(String key) {
        BaseLoginUser currentUser = IamSecurityUtils.getCurrentUser();
        return repositoryService.createModelQuery().modelKey(key).modelTenantId(currentUser.getTenantId()).singleResult();
    }

    @Override
    public byte[] getModelBpmnXML(String modelId) {
        return repositoryService.getModelEditorSource(modelId);
    }

    @Override
    @Transactional
    public void saveModelBpmnXml(Model model, String bpmnXml) {
        if (StrUtil.isEmpty(bpmnXml)) {
            return;
        }
        repositoryService.addModelEditorSource(model.getId(), StrUtil.utf8Bytes(bpmnXml));
    }

    private FormDesign validateFormConfig(BpmModelMetaInfoRespDTO  metaInfo) {
        // 校验表单存在
        if (ObjectUtils.isEmpty(metaInfo)) {
            throw new BusinessException("表单不存在！");
        }
        if (StringUtils.isEmpty(metaInfo.getFormType())){
            throw new BusinessException("无表单类型！");
        }
        //自定义表单
        if (FormTypeEnum.custom.getCode().equals(metaInfo.getFormType())){
            if(StringUtils.isEmpty(metaInfo.getFormId())){
                throw new BusinessException("表单不存在！");
            }
            FormDesign form = formDesignService.getEntity(metaInfo.getFormId());
            if (ObjectUtils.isEmpty(form)) {
                throw new BusinessException("表单不存在！");
            }
            return form;
        }
        //业务表单
        if ( FormTypeEnum.business.getCode().equals(metaInfo.getFormType())){
            if (StringUtils.isEmpty(metaInfo.getFormCreatePath())){
                throw new BusinessException("业务表单创建路径为空");
            }
        }
        return null;
    }

    private void validateBpmnXml(byte[] bpmnBytes) {
        BpmnModel bpmnModel = BpmnModelUtils.getBpmnModel(bpmnBytes);
        if (bpmnModel == null) {
            throw new BusinessException("流程模型不存在");
        }
        // 1. 没有 StartEvent
        StartEvent startEvent = BpmnModelUtils.getStartEvent(bpmnModel);
        if (startEvent == null) {
            throw new BusinessException("流程模型无开始任务节点");
        }
        // 2. 校验 UserTask 的 name 都配置了
        List<UserTask> userTasks = BpmnModelUtils.getBpmnModelElements(bpmnModel, UserTask.class);
        userTasks.forEach(userTask -> {
            if (StrUtil.isEmpty(userTask.getName())) {
                throw new BusinessException("用户任务名不存在");
            }
        });
    }
}
